/*
 * File:   main.cpp
 * Author: Lukas Gemela
 * Vstupní bod programu.
 *
 * Created on 29. říjen 2012, 19:21
 */
#include <cstdlib>
#include <string>
#include <iostream>
#include "Loader.h"
#include "EvoAlghorithm.h"
#include <memory>
#include "SVGGenerator.h"
#include "IFastDataApprox.h"
#include <array>
#include <limits>
#include "StringUtils.h"
#include "INPUT_PARAMS.h"
#include "INDIVIDUUM.h"
#include "anyoption.h"
#include "tbb/task_scheduler_init.h"
#define OUTPUT_POSTFIX_SVG ".svg"
#define OUTPUT_POSTFIX_TXT ".txt"

using namespace std;
/**
 * Výjimka, která je vyhozená v okamžiku, kdy je nějaký
 * problém se vstupními parametry.
 */
struct ParamException: public std::exception {
	std::string s;
	ParamException(std::string ss) :
			s(ss) {
	}
	const char* what() const throw () {
		return s.c_str();
	}
	~ParamException() throw() {}
};

/**
 * Vytiskne zadané parametry do konzole
 */
void printParameters(INPUT_PARAMS& params) {
	if (params.isPset)
		cout << "P: " << params.p << endl;
	if (params.isCset)
		cout << "C: " << params.c << endl;
	if (params.isKset)
		cout << "K: <" << params.kMin << ", " << params.kMax << ">" << endl;
	cout << "dt: <" << TimeUtils::convertWinToSec(params.dtMin) << ", "
			<< TimeUtils::convertWinToSec(params.dtMax) << ">" << endl;
	cout << "Thread: " << params.threadCount << endl;
	cout << "Step: " << params.step << endl;
	cout << "Generations: " << params.generation << endl;
	cout << "Population size: " << params.populationSize << endl;
	cout << "Crossrate: " << params.crossrate << endl;
	cout << "Mutation rate: " << params.mutratemin << " - " << params.mutratemax
			<< endl;
	cout << "Mutation step: " << params.mutstep << endl;

}

/**
 * Vypíše získané kořeny rovnice, maximální a průměrný absolutní rozdíl
 * mezi zadanými a vypočítanými hodnotami i(t) do souboru outputfilename
 */
void printValuesToFile(double maximumDiff, double averageDiff, INDIVIDUUM individuum,
		std::string outputfilename) {

	ofstream outputFile(outputfilename.c_str());
	outputFile << "p = " << individuum.p << endl;
	outputFile << "cg = " << individuum.cg << endl;
	outputFile << "c = " << individuum.c << endl;
	outputFile << "k = " << individuum.k << endl;
	outputFile << "dt = " << individuum.dt << endl;
	outputFile << "AVEGAGE DIFF = " << averageDiff << endl;
	outputFile << "MAX DIFF = " << maximumDiff << endl;
	outputFile.close();

}

/**
 * Načte vstupní data, zahájí výpočet a vytiskne SVG graf
 */
bool compute(INPUT_PARAMS params) {




	Loader loader(params.inputFile);
	if (!loader.load()) {
		return -1;
	}

	shared_ptr<IFastDataApprox> BGValues = loader.getBGValues();
	shared_ptr<IFastDataApprox> IGValues = loader.getIGValues();

	int bgLength = 0;
	int igLength = 0;
	HRESULT interfRes;
	interfRes = BGValues->Count(&bgLength);
	if(interfRes != S_OK) {cout << "Invalid data, method Count" << endl; return -1;}
	IGValues->Count(&igLength);
	if(interfRes != S_OK) {cout << "Invalid data, method Count" << endl; return -1;}
	int filled = -1;

	APPROXPOINT* bgAPPROXPOINTS = new APPROXPOINT[bgLength];
	APPROXPOINT* igAPPROXPOINTS = new APPROXPOINT[igLength];

	interfRes = BGValues->GetBuf(bgAPPROXPOINTS, OneSecond, bgLength, &filled);
	if(interfRes != S_OK) {cout << "Invalid data, method GetBuf" << endl; return -1;}

	cout << "Loaded data count: " << bgLength << endl;
	interfRes = IGValues->GetBuf(igAPPROXPOINTS, OneSecond, igLength, &filled);
	if(interfRes != S_OK) {cout << "Invalid data, method GetBuf" << endl; return -1;}


	//dopředu vypočítat ECG hodnoty
	shared_ptr<vector<double> > p_ECGValues = make_shared<vector<double> >(
			filled);
	for (int i = 0; i < filled; i++) {
		(*p_ECGValues)[i] = bgAPPROXPOINTS[i].y - igAPPROXPOINTS[i].y;
	}

	//inicializuj TBB
	tbb::task_scheduler_init init;
	init.initialize(params.threadCount);

		double maximumDiff = 0;
		double averageDiff = 0;

		EvoAlghorithm alg(BGValues, IGValues, p_ECGValues, params);
		INDIVIDUUM res = alg.compute(bgAPPROXPOINTS, igAPPROXPOINTS, filled,
				&averageDiff, &maximumDiff);
		APPROXPOINT* ignewAPPROXPOINTS = new APPROXPOINT[filled];
		for (int j = 0; j < filled; j++) {
			ignewAPPROXPOINTS[j].x = TimeUtils::convertSecToWin(j);
			ignewAPPROXPOINTS[j].y = res.p * bgAPPROXPOINTS[j].y
					+ res.cg * bgAPPROXPOINTS[j].y * ((*p_ECGValues)[j])
					+ res.c;
		}
		//Vykreslíme graf
		SVGGenerator svgGen;
		StringUtils::replace(params.inputFile, ".", "");
		svgGen.draw(ignewAPPROXPOINTS, bgAPPROXPOINTS, igAPPROXPOINTS, filled,
				params.inputFile + OUTPUT_POSTFIX_SVG);

		//Zapíšeme hodnoty do souboru
		printValuesToFile(maximumDiff, averageDiff, res, params.inputFile + OUTPUT_POSTFIX_TXT);



		init.terminate();

		delete[] ignewAPPROXPOINTS;

	delete[] bgAPPROXPOINTS;
	delete[] igAPPROXPOINTS;
	return 0;
}

/**
 * Parsování vstupních parametrů. V případě jejich neplatnosti
 * vyhodí ParamException
 */
void handleParameters(AnyOption *opt, INPUT_PARAMS *param) {

	if (opt->getValue("cvalue") != NULL) {
		param->c = StringUtils::convertToDouble(opt->getValue("cvalue"));
		param->isCset = true;
	}
	if (opt->getValue("pvalue") != NULL ) {
		param->p = StringUtils::convertToDouble(opt->getValue("pvalue"));
		param->isPset = true;
	}
	if (opt->getValue("step") != NULL) {
		param->step = StringUtils::convertToInt(opt->getValue("step"));
		if(param->step <= 0)
			throw ParamException("step cannot be equal or less than zero");

	}
	if (opt->getValue("workers") != NULL) {
		param->threadCount = StringUtils::convertToInt(opt->getValue("workers"));
	}
	if (opt->getValue("input") != NULL ) {
		param->inputFile = std::string(opt->getValue("input"));
	} else {
		throw ParamException("missing input file parameter");
	}
	if (opt->getValue("dtmin") != NULL) {
		param->dtMin = TimeUtils::convertSecToWin(StringUtils::convertToInt(opt->getValue("dtmin")));
	}
	if (opt->getValue("dtmax") != NULL) {
		param->dtMax = TimeUtils::convertSecToWin(StringUtils::convertToInt(opt->getValue("dtmax")));
	}
	if (param->dtMin > param->dtMax) {
		throw ParamException("dt range wrong format. Min value is greater than max value.");
	}
	if (opt->getValue("kmin") != NULL) {
		param->kMin = StringUtils::convertToDouble(opt->getValue("kmin"));
		param->isKset = true;
	}
	if (opt->getValue("kmax") != NULL) {
		param->kMax = StringUtils::convertToDouble(opt->getValue("kmax"));
		param->isKset = true;
	}
	if (param->kMin > param->kMax) {
		throw ParamException(
				"k range wrong format. Min value is greater than max value.");
	}
	if (opt->getValue("gen") != NULL) {
		param->generation = StringUtils::convertToInt(opt->getValue("gen"));
	}
	if (opt->getValue("popsize") != NULL) {
		param->populationSize = StringUtils::convertToInt(opt->getValue("popsize"));
	}
	if (opt->getValue("mutratemin") != NULL) {
		param->mutratemin = StringUtils::convertToDouble(opt->getValue("mutratemin"));
	}
	if (opt->getValue("mutratemax") != NULL) {
		param->mutratemax = StringUtils::convertToDouble(opt->getValue("mutratemax"));
	}
	if (opt->getValue("mutstep") != NULL) {
		param->mutstep = StringUtils::convertToDouble(opt->getValue("mutstep"));
	}
	if (param->mutratemin > param->mutratemax) {
		throw ParamException(
				"mutation range wrong format. Min value is greater than max value.");
	}
	if (opt->getValue("crossrate") != NULL) {
		param->crossrate = StringUtils::convertToDouble(opt->getValue("crossrate"));
	}

}



int main(int argc, char *argv[]) {

	INPUT_PARAMS params = { "", PARAM_THREAD_DEFAULT,
			TimeUtils::convertSecToWin(PARAM_DT_DEFAULT_MIN),
			TimeUtils::convertSecToWin(
			PARAM_DT_DEFAULT_MAX), false, 0.0, 0.0, false, 0.0, false, 0.0,
			PARAM_STEP_DEFAULT, PARAM_GENERATIONS_NUM, PARAM_POPULATION_SIZE,
			PARAM_MIN_MUTATION_RATE, PARAM_MAX_MUTATION_RATE,
			PARAM_MUTATION_STEP, PARAM_CROSS_RATE };
	AnyOption *opt = new AnyOption();
	opt->noPOSIX();

	//help message
	opt->addUsage("no param      Produce help message");
	opt->addUsage("--workers     Number of threads/processes");
	opt->addUsage("--cvalue      Set C value");
	opt->addUsage("--pvalue      Set P value");
	opt->addUsage("--dtmin       Minimum range DT");
	opt->addUsage("--dtmax       Maximum range DT");
	opt->addUsage("--kmin        Minimum range K");
	opt->addUsage("--kmax        Maximum range K");
	opt->addUsage("--step        Set step length peer worker");
	opt->addUsage("--input       Input CSV file");
	opt->addUsage("---Genetic parameters---");
	opt->addUsage("--gen         Number of generations");
	opt->addUsage("--popsize     Number of individuums in single population");
	opt->addUsage("--mutratemin  Minimum mutation rate");
	opt->addUsage("--mutratemax  Maximum mutation rate");
	opt->addUsage("--mutstep     Mutation step");
	opt->addUsage("--crossrate   Cross rate");

	opt->setOption("workers", 'w');
	opt->setOption("cvalue", 'c');
	opt->setOption("pvalue", 'p');
	opt->setOption("input", 'i');
	opt->setOption("step", 's');
	opt->setOption("kmin");
	opt->setOption("kmax");
	opt->setOption("dtmin");
	opt->setOption("dtmax");
	opt->setOption("gen");
	opt->setOption("popsize");
	opt->setOption("mutratemin");
	opt->setOption("mutratemax");
	opt->setOption("mutstep");
	opt->setOption("crossrate");
	opt->processCommandArgs(argc, argv);
	if(!opt->hasOptions()) {
		opt->printUsage();
		return 0;
	}
	handleParameters(opt, &params);

		printParameters(params);
		bool result = compute(params);
		//init.terminate();
		delete opt;
		return result;

}


